package com.aphrodite.ui.widgets.barcode;

import java.util.Hashtable;
import java.util.Vector;

import com.aphrodite.util.CharUtil;
import com.aphrodite.util.CharacterIterator;
import com.aphrodite.util.StringCharacterIterator;

/**
 * <p>
 * This is a concrete implementation of the Codabar barcode, AKA USD-4, Monarch,
 * NW-7 and 2of7. This implementation imposes no restrictions on your choice of
 * start and stop characters, so you will need to check that you are using chars
 * acceptable to your barcode scanner. This implementation does support the
 * traditional a, b, c, d, e, t, n and * start/stop chars. Omitting the
 * start/stop chars from your data will cause the barcode to use the default
 * start/stops chars, A and C, respectively. <br/> This implementation provides
 * no support for check digits (as they are not included in the Codabar
 * specification). However, many uses of Codabars mandate the use of a check
 * digit, and the algorithms used vary from application to application. The most
 * common algorithm is Mod-16. If you wish to implement a check digit in your
 * Codabar barcode, you must calculate it yourself and insert it into the data
 * to be encoded before the Stop char (or at the end of the data if you are
 * letting barbecue insert start and stop chars for you).
 * 
 * </p>
 */
final class CodabarBarcode extends LinearBarcode {
	private static final class ModuleTemplate {
		private static final Hashtable SET = new Hashtable();

		static {
			init();
		}

		/**
		 * Returns the module that represents the specified character.
		 * 
		 * @param key
		 *            The data character to get the encoding module for
		 * @return The module that encodes the given char
		 */
		private static Module getModule(String key) {
			Module module = null;
			module = (Module) SET.get(key);
			module.setSymbol(key);
			return module;
		}

		// /CLOVER:ON

		/**
		 * Initialise the module definitions.
		 */
		private static void init() {
			SET.put("0", new Module(new int[] { 1, 1, 1, 1, 1, 2, 2 }));
			SET.put("1", new Module(new int[] { 1, 1, 1, 1, 2, 2, 1 }));
			SET.put("2", new Module(new int[] { 1, 1, 1, 2, 1, 1, 2 }));
			SET.put("3", new Module(new int[] { 2, 2, 1, 1, 1, 1, 1 }));
			SET.put("4", new Module(new int[] { 1, 1, 2, 1, 1, 2, 1 }));
			SET.put("5", new Module(new int[] { 2, 1, 1, 1, 1, 2, 1 }));
			SET.put("6", new Module(new int[] { 1, 2, 1, 1, 1, 1, 2 }));
			SET.put("7", new Module(new int[] { 1, 2, 1, 1, 2, 1, 1 }));
			SET.put("8", new Module(new int[] { 1, 2, 2, 1, 1, 1, 1 }));
			SET.put("9", new Module(new int[] { 2, 1, 1, 2, 1, 1, 1 }));
			SET.put("-", new Module(new int[] { 1, 1, 1, 2, 2, 1, 1 }));
			SET.put("$", new Module(new int[] { 1, 1, 2, 2, 1, 1, 1 }));
			SET.put(":", new Module(new int[] { 2, 1, 1, 1, 2, 1, 2 }));
			SET.put("/", new Module(new int[] { 2, 1, 2, 1, 1, 1, 2 }));
			SET.put(".", new Module(new int[] { 2, 1, 2, 1, 2, 1, 1 }));
			SET.put("+", new Module(new int[] { 1, 1, 2, 2, 2, 2, 2 }));
			SET.put("A", new Module(new int[] { 1, 1, 2, 2, 1, 2, 1 }));
			SET.put("B", new Module(new int[] { 1, 1, 1, 2, 1, 2, 2 }));
			SET.put("C", new Module(new int[] { 1, 2, 1, 2, 1, 1, 2 }));
			SET.put("D", new Module(new int[] { 1, 1, 1, 2, 2, 2, 1 }));
		}

		// /CLOVER:OFF
		/**
		 * No public access.
		 */
		private ModuleTemplate() {}
	}

	/** The default codabar start character */
	public static final String DEFAULT_START = "A";

	/** The default codabar stop character */
	public static final String DEFAULT_STOP = "C";

	/**
	 * Indicates whether the given character is valid for this barcode or not.
	 * This basically just checks to see whether the key is in the list of
	 * encoded characters.
	 * 
	 * @param key
	 *            The key to check for validity
	 * @return True if the key is valid, false otherwise
	 */
	static boolean isValid(String key) {
		return ModuleTemplate.SET.containsKey(key);
	}

	private String label;

	/**
	 * Constructs a new Codabar barcode with thte specified data.
	 * 
	 * @param data
	 *            The data to encode. numbers (0-9), alphabets(A,B,C,D)
	 * @throws BarcodeException
	 *             If the data is invalid
	 */
	CodabarBarcode(String data, int height) throws BarcodeException {
		super(data, height);
		this.label = data;
		validateData();
	}

	private void addDefaultStartStopIfRequired() {
		StringBuffer newData = new StringBuffer();
		if (!CharUtil.isLetter(data.charAt(0))) {
			newData.append(DEFAULT_START);
		}
		newData.append(data);
		if (!CharUtil.isLetter(data.charAt(data.length() - 1))) {
			newData.append(DEFAULT_STOP);
		}
		data = newData.toString();
	}

	/**
	 * Calculates the check sum digit for the barcode.
	 * 
	 * @return Null - Codabar has no checksum
	 */
	Module calculateChecksum() {
		return null; // No checksum - return null
	}

	private void checkStartStop(char c, int index) throws BarcodeException {
	// if (StringUtil.isLetter(c) && index > 0 && index < data.length() - 1) {
	// System.out.println("current char " + c);
	// throw new BarcodeException(c +
	// " is only allowed as the first and last characters for Codabar barcodes"
	// );
	// }
	}

	public void cleanUp() {
	// TODO Auto-generated method stub

	}

	/**
	 * Encodes the data of the barcode into bars.
	 * 
	 * @return The encoded bar data
	 */
	Module[] encodeData() {
		Vector modules = new Vector();
		for (int i = 0; i < data.length(); i++) {
			if (i > 0) {
				modules.addElement(new SeparatorModule(1));
			}
			char c = data.charAt(i);
			Module module = ModuleTemplate.getModule(String.valueOf(c));
			modules.addElement(module);
		}
		Module[] ret = new Module[modules.size()];
		modules.copyInto(ret);
		return ret;
		// return (Module[]) modules.toArray(new Module[0]);
	}

	/**
	 * Returns the text label to be displayed underneath the barcode.
	 * 
	 * @return The barcode label
	 */
	public String getLabel() {
		return label;
	}

	/**
	 * Returns the post-amble for the barcode.
	 * 
	 * @return A BlankModule
	 */
	Module getPostAmble() {
		if (drawingQuietSection) {
			return new BlankModule(0);
		} else {
			return null;
		}
	}

	/**
	 * Returns the pre-amble for the barcode.
	 * 
	 * @return A BlankModule
	 */
	Module getPreAmble() {
		if (drawingQuietSection) {
			return new BlankModule(0);
		} else {
			return null;
		}
	}

	private void replaceTraditionalStartStopWithModern() {
		data = data.replace('a', 'A');
		data = data.replace('t', 'A');
		data = data.replace('b', 'B');
		data = data.replace('n', 'B');
		data = data.replace('c', 'C');
		data = data.replace('*', 'C');
		data = data.replace('d', 'D');
		data = data.replace('e', 'D');
	}

	private void validateData() throws BarcodeException {
		replaceTraditionalStartStopWithModern();
		addDefaultStartStopIfRequired();
		int index = 0;
		StringBuffer buf = new StringBuffer();
		StringCharacterIterator iter = new StringCharacterIterator(data);
		for (char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
			if (!CharUtil.isWhitespace(c)) {
				// if (!ModuleFactory.isValid(String.valueOf(c))) {
				// throw new BarcodeException(c +
				// "blank space is not a valid character for Codabar encoding");
				// }
				checkStartStop(c, index);
				buf.append(c);
			}
			index += 1;
		}
		data = buf.toString();
	}

}
